package rme.extras;

import rme.*;
import arcademis.*;
import java.util.*;

/**
 * This connector performs synchronous connections, but, whenever possible it
 * reuses an old connection instead of creating a new one. In this case,
 * channels are stored on a pool of connections.
 */
public class ChannelPoolConnector extends Connector {

	private Hashtable channelPool = new Hashtable();

	/**
	 * This method establishes a connection with a specified acceptor using an
	 * old channel if there is one, or creating a new one, if it is not
	 * possible to reuse any channel.
	 * @param h the service handler that will be used to process the channel after
	 * it has been generated by the connector.
	 * @param epid the location of the acceptor that will be contacted in order to
	 * generate a channel with this connector.
	 * @throws NetworkException in the case some error takes place in the
	 * transport layer.
	 */
	public void connect(ServiceHandler h, Epid epid) throws NetworkException {
		Channel ch = lookForOldChannel(epid);
		if(ch == null) {
			// creates a new channel and connect it to the given epid.
			ch = OrbAccessor.getChannel();
			ch.connect(epid);
			saveNewChannel(ch, epid);
		}
		h.open(ch);
	}

	/**
	 * For eficience reasons, the ping message is not been used in order to test
	 * if the server is stil alive after some idle time. If this time is half the
	 * time the server keeps the channel, than a new connection is created.
	 */
	private Channel lookForOldChannel(Epid epid) throws NetworkException {
		TableEntry i = (TableEntry)channelPool.get(epid);
		if(i == null)
			return null;
		if( System.currentTimeMillis() - i.instantOfUtilization > (RmeConstants.TOLERANCE_TIME/2) ) {
			i.channel.close();
			i.channel.connect(epid);
		}
		return i.channel;
	}

	private void saveNewChannel(Channel ch, Epid epid) {
		TableEntry i = new TableEntry(ch, System.currentTimeMillis());
		channelPool.put(epid, i);
	}

	private class TableEntry {
		public Channel channel = null;
		public long instantOfUtilization = 0;

		public TableEntry(Channel ch, long time) {
			channel = ch;
			instantOfUtilization = time;
		}
	}

	/**
	 * Tests if there is a server entity listening for messages in the other
	 * side of the channel. This test is performed by means of a ping message.
	 * If such message originates a replay, then it is possible to conclude
	 * that it is possible to use the channel.
	 */
	private boolean isAlive(Channel ch) {
		ChannelVerifier cv = (ChannelVerifier)OrbAccessor.getServiceHandler(ArcademisConstants.CHANNEL_VERIFIER);
		cv.setProtocol(OrbAccessor.getProtocol());
		cv.open(ch);
		return cv.isOpen();
	}
}